package com.sheledon.flyCat.core.ioc;

import com.sheledon.flyCat.common.utils.ReflectionUtils;
import com.sheledon.flyCat.core.configuration.ConfigurationManager;
import com.sheledon.flyCat.core.exception.BeanCreationException;
import com.sheledon.flyCat.core.exception.CircularException;
import com.sheledon.flyCat.core.exception.NotFoundBeanException;
import com.sheledon.flyCat.factory.ClassFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 不论是SpringBoot还是flyCat，在注入构造器参数和方法参数的时候
 * 都是根据类型的SimpleName去寻找bean的
 * @author sheledon
 */
public class SingletonRegister {
    protected static final HashMap<String,Object> earlySingletonObjects = new HashMap<>();
    private static final HashSet<String> creatingBeans = new HashSet<>();
    public static Object registerSingleton(String beanName,Class<?> clazz){
        isWillInIoc(clazz);
        if (earlySingletonObjects.containsKey(beanName)){
            return earlySingletonObjects.get(beanName);
        }
        if (hasDefaultConstruct(clazz)){
            Object bean = ReflectionUtils.newInstance(clazz);
            earlySingletonObjects.put(beanName,bean);
            return bean;
        }
        if (clazz.getConstructors().length!=1) {
            throw new BeanCreationException("not found default constructor: " + clazz.getName());
        }
        // 必须使用 constructor 构建了
        Object bean = createBean(clazz);
        earlySingletonObjects.put(beanName,bean);
        return bean;
    }
    //这里并没有解决 configuration相互之间依赖的问题
    public static Object registerSingleton(String beanName,Method method,Object target){
        if (earlySingletonObjects.containsKey(beanName)){
            return earlySingletonObjects.get(beanName);
        }
        Parameter[] parameters = method.getParameters();
        List<Object> args = new ArrayList<>(parameters.length);
        for (Parameter parameter : parameters) {
            String parClassName = parameter.getType().getSimpleName();
            Object par;
            if (ConfigurationManager.BEAN_METHOD.containsKey(parClassName)){
                Method parMethod = ConfigurationManager.BEAN_METHOD.get(parClassName);
                par = registerSingleton(parClassName,parMethod
                        ,BeanFactory.getBean(parMethod.getDeclaringClass().getSimpleName()));
            }else{
                par = registerSingleton(parClassName,parameter.getType());
            }
            args.add(par);
        }
        Object bean = ReflectionUtils.executeMethod(method,target,args.toArray());
        earlySingletonObjects.put(beanName,bean);
        return bean;
    }

    private static Object createBean(Class<?> clazz){
        judgeCircular(clazz);
        creatingBeans.add(clazz.getName());
        Constructor<?>[] constructors = clazz.getConstructors();
        Parameter[] parameters = constructors[0].getParameters();
        List<Object> args = new ArrayList<>(parameters.length);
        for (Parameter parameter : parameters) {
            //构造器注入，找到的时候类名对应的bean，如果找不到则会抛出异常,这一点和springboot是一致的
            String parName = parameter.getType().getSimpleName();
            Object par = BeanFactory.getBean(parName);
            if (par==null){
                par = earlySingletonObjects.get(parName);
            }
            if (par!=null){
                args.add(par);
            }else{
                //这里的问题就是，根据简单类名使用默认构造函数构建符合参数类型的bean，但是用户并没有注册以这个beanName为名的bean
                //采用的解决的办法就是，在所有bean构造完成之后，检查二级缓存中name和beanfactory中beanname是否一致
                //如果不一致，说明出现了错误
                args.add(registerSingleton(parName,parameter.getType()));
            }
        }
        creatingBeans.remove(clazz.getName());
        return ReflectionUtils.newInstance(constructors[0],args.toArray());
    }
    private static boolean hasDefaultConstruct(Class<?> clazz){
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            if (constructor.getParameterCount()==0){
                return true;
            }
        }
        return false;
    }
    private static void judgeCircular(Class<?> clazz){
        if (creatingBeans.contains(clazz.getName())){
            throw new CircularException();
        }
    }
    private static void isWillInIoc(Class<?> clazz){
        boolean contains = false;
        for (Map.Entry<Class<? extends Annotation>, Set<Class<?>>> entry : ClassFactory.CLASS_FACTORY.entrySet()) {
            for (Class<?> aClass : entry.getValue()) {
                if (aClass.equals(clazz)){
                    contains=true;
                    break;
                }
            }
            if (contains) {
                break;
            }
        }
        if (!contains){
            throw new NotFoundBeanException("not found bean from ioc: " + clazz.getName());
        }
    }

}
